Terraform WorkspacesとAssume Roleで複数AWSアカウントに同じAWSアーキテクチャをプロビジョニングする
やりたいこと
シングルテナントで提供するサービスを構想しています。つまり複数の(しかも結構な数の)AWSアカウントに同じAWSの構成をプロビジョニングする必要があります。これをTerraformで実現したいです。Stateファイルは各テナント(アカウント)毎に分けたいです。
実装方針
Terraform workspacesとIAMのAssume Roleを使って実現します。
テナントごとにworkspaceを作成し、aws providerのcredentialの設定をAssume Roleにし、workspace毎にAssume Role先を切り替えます。Assume Role元は、このプロビジョニング専用のアカウント内のIAMエンティティ(IAM RoleかIAM User)です。各テナントのStateファイルはプロビジョニング専用のアカウント内のS3バケット内に保存されます。
実装
※今回はサンプルなので、各アカウントのProvisioned Resources
はS3バケット一つだけとします。
Assume Role元のIAM Roleを作成
Provisioning Accountにて、プロビジョニング先の各アカウントのロールにAssume RoleするIAM Roleterraform-role
を作成します。
アタッチするポリシーは以下です。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::*:role/multiple-provision-test-role" }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::kazue-s3-backend" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::kazue-s3-backend/multiple-provision-test.tfstate", "arn:aws:s3:::kazue-s3-backend/env:/*/multiple-provision-test.tfstate" ] } ] }
1つ目のAllowステートメントが各アカウントのIAM RoleにAssume Roleするための権限です。Resource句のARNの、AWSアカウントIDを入れる部分を*
にしているのがポイントです。残り2のステートメントはS3 backendへアクセスするための権限です。
プロビジョニング先アカウントにIAM Roleを作成
プロビジョニング先の各アカウントにAssume Role先のIAM Roleを作成します。IAM Role名はmultiple-provision-test-role
とすべて同じにします。
信頼Policy
Principal句で、先程作ったProvisioning Account内のIAM RoleからのみAssume Roleを許可します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:role/terraform-role" }, "Action": "sts:AssumeRole" } ] }
IAM Policy(アクセス権限)
AWS 管理ポリシーのAdministratorAccessをアタッチします。
Terraformコードを作成
S3バケットを一つ作成するコードです。
terraform { required_version = "= 0.14.7" required_providers { aws = { source = "hashicorp/aws" version = "3.29.1" } } backend "s3" { bucket = "kazue-s3-backend" key = "multiple-provision-test.tfstate" region = "ap-northeast-1" } } provider "aws" { region = "ap-northeast-1" assume_role { role_arn = "arn:aws:iam::${terraform.workspace}:role/multiple-provision-test-role" session_name = "terraform" } } resource "aws_s3_bucket" "test" { bucket_prefix = "${terraform.workspace}-kazue-test-" }
ポイントはAWS Providerの assume_role
ブロックのrole_arn
です。アカウントID指定部分を${terraform.workspace}
と動的にしています。
backend用S3バケットも予め作成しておきましょう。
Workspace作成
事前に先のProvisioning Accountのロールを引き受けている状態にしておきます。
$ aws sts get-caller-identity { "UserId": "XXXXXXXXXXXXXXXXXXXXX:kazue", "Account": "0123456789012", "Arn": "arn:aws:sts::0123456789012:assumed-role/terraform-role/kazue" }
terraform init
$ terraform init Initializing the backend... Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Installing hashicorp/aws v3.29.1... - Installed hashicorp/aws v3.29.1 (signed by HashiCorp) Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
terraform workspace new
$ terraform workspace new 111111111111 Created and switched to workspace "111111111111"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.
プロビジョニング
$ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_s3_bucket.test will be created + resource "aws_s3_bucket" "test" { + acceleration_status = (known after apply) + acl = "private" + arn = (known after apply) + bucket = (known after apply) + bucket_domain_name = (known after apply) + bucket_prefix = "111111111111-kazue-test-" + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + region = (known after apply) + request_payer = (known after apply) + website_domain = (known after apply) + website_endpoint = (known after apply) + versioning { + enabled = (known after apply) + mfa_delete = (known after apply) } } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions in workspace "111111111111"? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_s3_bucket.test: Creating... aws_s3_bucket.test: Creation complete after 3s [id=111111111111-kazue-test-20210330111653262000000001] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
S3バケットが作成されました!
別のアカウントにプロビジョニング
今回はterraform init
は不要です。
$ terraform workspace new 222222222222 Created and switched to workspace "222222222222"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration. $ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_s3_bucket.test will be created + resource "aws_s3_bucket" "test" { + acceleration_status = (known after apply) + acl = "private" + arn = (known after apply) + bucket = (known after apply) + bucket_domain_name = (known after apply) + bucket_prefix = "222222222222-kazue-test-" + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + region = (known after apply) + request_payer = (known after apply) + website_domain = (known after apply) + website_endpoint = (known after apply) + versioning { + enabled = (known after apply) + mfa_delete = (known after apply) } } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions in workspace "222222222222"? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes aws_s3_bucket.test: Creating... aws_s3_bucket.test: Creation complete after 3s [id=222222222222-kazue-test-20210330112053303300000001] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Stateファイルの構成は以下のようになります。
aws s3 ls s3://kazue-s3-backend --recursive --human-readable 2021-03-30 20:16:57 1.8 KiB env:/111111111111/multiple-provision-test.tfstate 2021-03-30 20:20:57 1.8 KiB env:/222222222222/multiple-provision-test.tfstate
まとめ
Terraform WorkspacesとAssume Roleを使って、複数のAWSアカウントに同じ構成をプロビジョニングする方法をご紹介しました。今後はAssume Role元のRoleをCodeBuildに引き受けさせて、プロビジョニングの自動化を検討していきます。